using System;
using System.Collections.Generic;
using System.Diagnostics;

using Atomic.Libraries;
using Atomic.Libraries.Mathematics;
using Atomic.Structures;
using Atomic.Structures.Symmetries;

#pragma warning disable // FIXME

namespace Atomic.Clusters
{
	/// <summary>
	/// Represents the equivalence class of cluster defined with respect to symmetry equivalence. This corresponds to the multiplicity of the cluster.
	/// </summary>
	[Serializable]
	[DebuggerDisplay("{DebuggerDisplay}")]
	public class ClusterClass : Cluster
	{
		private int multiplicity;
		private ReadOnlyList<Cluster> equivalentClusters;
		private Structure lattice;
		public IEnumerable<SpaceGroupSymmetryOperation> symmetries; // FIXME!!!

		internal ClusterClass(Structure lattice, IEnumerable<ClusterPoint> points, double maxDistance, int multiplicity, IEnumerable<SpaceGroupSymmetryOperation> symmetries) // FIXME: when repeating: use translation vectors to build larger symmetries!!
			: base(lattice, points, maxDistance)
		{
			this.multiplicity = multiplicity;
			this.lattice = lattice;
			this.symmetries = symmetries;
		}

		private IEnumerable<Cluster> FindEquivalentClusters()
		{
			/*List<Cluster> clusters = new List<Cluster>();

			if (Points.Count == 2)
			{
				// Eliminate clusters being equivalent by translations of the lattice vectors or permutations of the points.
				DistinctClusterCollection c = new DistinctClusterCollection(lattice, symmetries);
				foreach (Symmetry symmetry in symmetries)
				{
					Vector3 p1 = Points[0].Position;
					Vector3 p2 = Points[1].Position;

					p1 = symmetry.Rotation * p1 + symmetry.Translation;
					p2 = symmetry.Rotation * p2 + symmetry.Translation;

					if (c.Add(p1, p2))
					{
						double d = Vector3.Norm(lattice.Basis.Map * p1 - lattice.Basis.Map * p2);
						d -= MaxDistance;
						if (Math.Abs(d) > 1e-6)
						{
							throw new Exception("FIMXE ERROR");
						}
						//double d = Vector3.Norm(lattice.Basis.Map * p1 - lattice.Basis.Map * p2);
						//clusters.Add(new Cluster(new ClusterPoint[] { CreateClusterPoint(p1), CreateClusterPoint(p2) }, MaxDistance));
						//clusters.Add(new Cluster(new ClusterPoint[] { new ClusterPoint(Points[0].LatticeSite, p1, null, Points[0].CorrelationFunction), new ClusterPoint(Points[1].LatticeSite, p2, null, Points[1].CorrelationFunction) }, MaxDistance));
						clusters.Add(new Cluster(Lattice, new ClusterPoint[] { CreateClusterPoint(p1, Points[0].ClusterFunction), CreateClusterPoint(p2, Points[1].ClusterFunction) }, MaxDistance));
					}
				}
			}
			else
			{
				throw new NotImplementedException();
			}

			if (multiplicity != 0 && multiplicity != clusters.Count)
			{
				throw new Exception("Cluster equivalence multiplicity disagree.");
			}

			return clusters;*/
			throw new NotImplementedException();
		}

		private ClusterPoint CreateClusterPoint(SpaceVector position, ClusterFunctionFactor function) // ..., Atom atom
		{
			return new ClusterPoint(Identify(position), position, function);
		}

		private Site Identify(SpaceVector position)
		{
			/*// FIXME check atom type also?

			// Only IEEE round-off errors expected in this identification.
			double tol = 1.0e-10;

			foreach (Site site in lattice.Sites)
			{
				// Try solving for:
				// position == site.Position + a * structure.Lattice.A + ...
				Vector3 d = lattice.Lattice.Decompose(position - site.Position);
				if (Math.Abs(d[0] - (int)Math.Round(d[0])) < tol && Math.Abs(d[1] - (int)Math.Round(d[1])) < tol && Math.Abs(d[2] - (int)Math.Round(d[2])) < tol)
				{
					return site;
				}
			}

			throw new Exception("Couldn't identify site positions.");*/
			throw new NotImplementedException();
		}

		/// <summary>
		/// The list of equivalent clusters in this equivalence class of clusters as defined by the symmetries of the lattice.
		/// </summary>
		public ReadOnlyList<Cluster> EquivalentClusters
		{
			get
			{
				if (equivalentClusters == null)
				{
					equivalentClusters = new ReadOnlyList<Cluster>(FindEquivalentClusters());
				}

				return equivalentClusters;
			}
		}

		/// <summary>
		/// Cluster multiplicity. This is simply the number of equivalent clusters.
		/// </summary>
		public int Multiplicity
		{
			get
			{
				if (multiplicity == 0)
				{
					// This forces full computation of equivalent clusters.
					multiplicity = EquivalentClusters.Count;
				}

				return multiplicity;
			}
		}

		private string DebuggerDisplay
		{
			get
			{
				return ToString();
			}
		}

		public static ClusterExpansionTerm operator |(ClusterClass cluster, double coefficient)
		{
			return new ClusterExpansionTerm(cluster, coefficient);
		}

		private class DistinctClusterCollection
		{
			private Structure lattice;
			private IEnumerable<SpaceGroupSymmetryOperation> symmetries;
			private List<DistinctCluster> clusters;

			public DistinctClusterCollection(Structure lattice, IEnumerable<SpaceGroupSymmetryOperation> symmetries)
			{
				this.lattice = lattice;
				this.symmetries = symmetries;

				clusters = new List<DistinctCluster>();
			}

			public bool Add(SpaceVector p1, SpaceVector p2)
			{
				DistinctCluster c1 = new DistinctCluster(lattice, symmetries, p1, p2);
				foreach (DistinctCluster c2 in clusters)
				{
					if (c2.Equals(c1))
					{
						return false;
					}
				}

				clusters.Add(c1);
				return true; // was added
			}

			public int Count
			{
				get
				{
					return clusters.Count;
				}
			}
		}

		private class DistinctCluster
		{
			private Structure lattice;
			//private IEnumerable<Symmetry> symmetries;
			//public Vector3 p1, p2;
			private List<SpaceVector> points;

			public DistinctCluster(Structure lattice, IEnumerable<SpaceGroupSymmetryOperation> symmetries, SpaceVector p1, SpaceVector p2)
			{
				this.lattice = lattice;
				//this.symmetries = symmetries;
				//this.p1 = p1;
				//this.p2 = p2;

				points = new List<SpaceVector>();
				points.Add(p1);
				points.Add(p2);
				points.Sort(Comparer);
			}

			private double Reduce(double x)
			{
				return x - (int)x;
			}

			private SpaceVector Reduce(SpaceVector p)
			{
				/*p = lattice.Lattice.Decompose(p);
				return new Vector3(Reduce(p.X), Reduce(p.Y), Reduce(p.Z));
				//return p;*/
				throw new NotImplementedException();
			}

			private int Comparer(SpaceVector p1, SpaceVector p2)
			{
				//p1 = Reduce(p1);
				//p2 = Reduce(p2);

				int c;
				if ((c = p1.X.CompareTo(p2.X)) == 0)
				{
					if ((c = p1.Y.CompareTo(p2.Y)) == 0)
					{
						c = p1.Z.CompareTo(p2.Z);
					}
				}

				return c;
			}

			public bool Equals(DistinctCluster other)
			{
				return Equals(other, 1.0e-5);
			}

			public bool Equals(DistinctCluster other, double tol)
			{
				//return Equals(other.points[0], other.points[1], tol);// || Equals(other.points[1], other.points[0], tol);
				return Equals(other.points[0], other.points[1], tol) || Equals(other.points[1], other.points[0], tol);
				//return Equals(other.p1, other.p2, tol) || Equals(other.p2, other.p1, tol);
			}

			private bool Equals(SpaceVector q1, SpaceVector q2, double tol)
			{
				/*// Map back to the site on the (expanded) lattice and use that for comparison instead????

				Vector3 d = lattice.Lattice.Decompose(points[0] - q1);

				if (Math.Abs(d[0] - (int)Math.Round(d[0])) < tol && Math.Abs(d[1] - (int)Math.Round(d[1])) < tol && Math.Abs(d[2] - (int)Math.Round(d[2])) < tol)
				{
					// Now: A*d = p1-q1, d \in Z^3
					// 
					// Use same translation on the other point.

					Vector3 p2x = q2 + lattice.Lattice.Map * d;

					if (Vector3.Norm(points[1] - p2x) < tol)
					{
						return true;
					}
				}

				return false;*/
				throw new NotImplementedException();
			}

			private bool Equals2(SpaceVector q1, SpaceVector q2, double tol)
			{
				/*Vector3 d = lattice.Lattice.Decompose(points[0] - q1);

				if (Math.Abs(d[0] - (int)Math.Round(d[0])) < tol && Math.Abs(d[1] - (int)Math.Round(d[1])) < tol && Math.Abs(d[2] - (int)Math.Round(d[2])) < tol)
				{
					// Now: A*d = p1-q1, d \in Z^3
					// 
					// Use same translation on the other point.

					Vector3 p2x = q2 + lattice.Lattice.Map * d;

					if (Vector3.Norm(points[1] - p2x) < tol)
					{
						return true;
					}
				}

				return false;*/
				throw new NotImplementedException();
			}
		}
	}
}
